/*
 * The MIT License
 * Copyright (c) 2011 kc4.org
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */
package org.kc4.sso.filter;

import java.io.IOException;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.kc4.sso.auth.SSOClientInitParameters;
import org.kc4.sso.auth.SSOContextParameters;
import org.kc4.sso.auth.section.InterruptedSectionException;
import org.kc4.sso.auth.section.client.NotExistCipherOnInvalidUserObjectSection;
import org.kc4.sso.auth.section.client.SucceededSignonOnInvalidUserObjectSection;
import org.kc4.sso.auth.section.client.ValidUserObjectSection;
import org.kc4.util.FilterInitParametersMap;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * シングルサインオンクライアントフィルタ。<br />
 * フィルタの適用例は次のとおりです。
 * <pre>{@code
 *<filter>
 *	<display-name>SSOClientFilter</display-name>
 *	<filter-name>SSOClientFilter</filter-name>
 *	<filter-class>org.kc4.sso.filter.SSOClientFilter</filter-class>
 *	<init-param>
 *		<description>
 *			認証されたユーザーオブジェクトのクラスを設定します。
 *			必須設定項目です。
 *		</description>
 *		<param-name>UserObject</param-name>
 *		<param-value>org.kc4.sso.auth.AuthorizedUser</param-value>
 *	</init-param>
 *	<init-param>
 *		<description>
 *			フィルタから除外するパターンを記述します。
 *			ワイルドカードは先頭と末尾のみに使用することができます。
 *		</description>
 *		<param-name>IgnoreURI</param-name>
 *		<param-value>/css/*,/js/*</param-value>
 *	</init-param>
 *	<!--
 *	<init-param>
 *		<description>
 *			静的コールバックを設定します。
 *		</description>
 *		<param-name>StaticCallback</param-name>
 *		<param-value>http://hostname/sso-client/static.html</param-value>
 *	</init-param>
 *	<init-param>
 *		<description>
 *			コールバックのレスポンスURLパラメーター名を設定します。
 *			初期値は "_callback" です。
 *		</description>
 *		<param-name>UrlResponseNameOfCallback</param-name>
 *		<param-value>_callback</param-value>
 *	</init-param>
 *	<init-param>
 *		<description>
 *			公開鍵のレスポンスURLパラメーター名を設定します。
 *			初期値は "_pubkey" です。
 *		</description>
 *		<param-name>UrlResponseNameOfPublickey</param-name>
 *		<param-value>_pubkey</param-value>
 *	</init-param>
 *	<init-param>
 *		<description>
 *			コンテキストURLのレスポンスURLパラメーター名を設定します。
 *			初期値は "_ctxurl" です。
 *		</description>
 *		<param-name>UrlResponseNameOfContextUrl</param-name>
 *		<param-value>_ctxurl</param-value>
 *	</init-param>
 *	<init-param>
 *		<description>
 *			認証トークンのリクエストURLパラメーター名を設定します。
 *			初期値は "_auth" です。
 *		</description>
 *		<param-name>UrlRequestNameOfAuthorizationToken</param-name>
 *		<param-value>_auth</param-value>
 *	</init-param>
 *	<init-param>
 *		<description>
 *			暗号化された共通鍵のリクエストURLパラメーター名を設定します。
 *			初期値は "_comkey" です。
 *		</description>
 *		<param-name>UrlRequestNameOfEncryptedCommonkey</param-name>
 *		<param-value>_comkey</param-value>
 *	</init-param>
 *	<init-param>
 *		<description>
 *			認証トークンのレスポンスフォームパラメーター名を設定します。
 *			初期値は "_auth" です。
 *		</description>
 *		<param-name>FormResponseNameOfAuthorizationToken</param-name>
 *		<param-value>_auth</param-value>
 *	</init-param>
 *	<init-param>
 *		<description>
 *			ユーザーオブジェクトのセッション属性名を設定します。
 *			初期値は "_userobject" です。
 *		</description>
 *		<param-name>SessionNameOfUserObject</param-name>
 *		<param-value>_userobject</param-value>
 *	</init-param>
 *	<init-param>
 *		<description>
 *			認証トークンのセッション属性名を設定します。
 *			初期値は "_auth" です。
 *		</description>
 *		<param-name>SessionNameOfAuthorizationToken</param-name>
 *		<param-value>_auth</param-value>
 *	</init-param>
 *	<init-param>
 *		<description>
 *			暗号器のセッション属性名を設定します。
 *			初期値は "_cipher" です。
 *		</description>
 *		<param-name>SessionNameOfCipher</param-name>
 *		<param-value>_cipher</param-value>
 *	</init-param>
 *	-->
 *</filter>
 *<filter-mapping>
 *	<filter-name>SSOClientFilter</filter-name>
 *	<url-pattern>/*</url-pattern>
 *</filter-mapping>
 * }</pre>
 *
 * @author yoichi kikuchi
 * @since 1.0.0
 */
public class SSOClientFilter extends IgnorableFilter {

	private static final Logger LOGGER = LoggerFactory.getLogger(SSOClientFilter.class);

	/**
	 * Default constructor.
	 */
	public SSOClientFilter() {
	}

	/**
	 * @see Filter#init(FilterConfig)
	 * @see IgnorableFilter#init(FilterConfig)
	 */
	@Override
	public void init(FilterConfig filterConfig) throws ServletException {

		super.init(filterConfig);

		// コンテキストのロード
		SSOContextParameters.getInstance(filterConfig.getServletContext());

		// フィルター構成のロード。
		SSOClientInitParameters.getInstance(
			new FilterInitParametersMap(filterConfig));

		if (SSOContextParameters.getInstance().getProviderURL() == null ||
			SSOContextParameters.getInstance().getProviderURL().isEmpty()) {

			throw new IllegalStateException("SSO-Client is required context-param, \"ProviderURL\" setting.");
		}
	}

	/**
	 * @see Filter#destroy()
	 */
	@Override
	public void destroy() {}

	/**
	 * @see Filter#doFilter(ServletRequest, ServletResponse, FilterChain)
	 */
	@Override
	public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {

		if (request instanceof HttpServletRequest && response instanceof HttpServletResponse) {

			HttpServletRequest req = (HttpServletRequest) request;
			HttpServletResponse res = (HttpServletResponse) response;

			if (LOGGER.isTraceEnabled()) {

				LOGGER.trace(String.format("ignore filter uri=\"%s\"", req.getRequestURI()));
			}

			// フィルタ適用外のURIを処理
			if (this.isIgnoreURI(req)) {

				chain.doFilter(request, response);
				return;
			}

			// ユーザーオブジェクト有効
			try {

				new ValidUserObjectSection(req, res).execute();
			}
			catch (InterruptedSectionException e) {

				if (!e.isSentError()) {

					chain.doFilter(request, response);
				}

				return;
			}

			// ユーザーオブジェクト無効 > 暗号器有効
			try {

				new SucceededSignonOnInvalidUserObjectSection(req, res).execute();
			}
			catch (InterruptedSectionException e) {

				if (!e.isSentError()) {

					chain.doFilter(request, response);
				}

				return;
			}

			// ユーザーオブジェクト無効 > 暗号器無効
			try {

				new NotExistCipherOnInvalidUserObjectSection(req, res).execute();
			}
			catch (InterruptedSectionException e) {

				return;
			}
		}

		chain.doFilter(request, response);
	}
}
